/*******************************************************************************
 * Copyright 2011-2012 Dik Grapendaal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package sh.grapendaal.tsuushin.core;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import sh.grapendaal.tsuushin.message.IncomingMessage;
import sh.grapendaal.tsuushin.message.MessageFactory;
import sh.grapendaal.tsuushin.message.MessageType;
import sh.grapendaal.tsuushin.message.OutgoingMessage;
import sh.grapendaal.tsuushin.message.outgoing.OutgoingJoinMessage;
import sh.grapendaal.tsuushin.message.outgoing.OutgoingNickMessage;
import sh.grapendaal.tsuushin.message.outgoing.PongMessage;
import sh.grapendaal.tsuushin.message.outgoing.UserMessage;
import sh.grapendaal.tsuushin.service.BotService;
import sh.grapendaal.tsuushin.service.ModuleService;
import sh.grapendaal.tsuushin.socket.IrcSocket;
import sh.grapendaal.tsuushin.socket.IrcSocketFactory;
import sh.grapendaal.tsuushin.socket.IrcSocket.DisconnectReason;
import sh.grapendaal.tsuushin.tracking.Channel;
import sh.grapendaal.tsuushin.tracking.Context;

public class IrcBot implements IrcSocket.Listener, Runnable {
	private static final Logger logger = LoggerFactory.getLogger(IrcBot.class);

	@Autowired
	private BotService botService;

	@Autowired
	private ModuleService moduleService;

	private IrcSocket socket;
	private Network network;
	private BotInformation information = new BotInformation();
	private List<Channel> channels = new ArrayList<Channel>();

	private class BotInformation {
		private String nickname;
		private String username;
		private String realname;

		public String getNickname() {
			return nickname;
		}

		public void setNickname(String nickname) {
			this.nickname = nickname;
		}

		public String getUsername() {
			return username;
		}

		public void setUsername(String username) {
			this.username = username;
		}

		public String getRealname() {
			return realname;
		}

		public void setRealname(String realname) {
			this.realname = realname;
		}
	}

	public Network getNetwork() {
		return network;
	}

	public void setNetwork(Network network) {
		this.network = network;
	}

	public void setNickname(String nickname) {
		information.setNickname(nickname);
	}

	public void setUsername(String username) {
		information.setUsername(username);
	}

	public void setRealname(String realname) {
		information.setRealname(realname);
	}

	public List<Channel> getChannels() {
		return channels;
	}

	public void setChannels(List<Channel> channels) {
		this.channels = channels;
	}

	public void addChannel(String channelName) {
		this.channels.add(new Channel(channelName));
	}

	public void addChannel(String channelName, String password) {
		this.channels.add(new Channel(channelName, password));
	}

	public void addChannel(Channel channel) {
		this.channels.add(channel);
	}

	public void removeChannel(String channelName) {
		this.channels.remove(new Channel(channelName));
	}

	public void removeChannel(Channel channel) {
		this.channels.remove(channel);
	}

	public void connect() throws IOException {
		socket = IrcSocketFactory.createSocket(network.getServers().get(0));
		socket.addListener(this);
		socket.connect();
	}

	public void disconnect() {
		socket.close();
	}

	@Override
	public void run() {
		try {
			this.connect();
			synchronized (this) {
				this.wait();
			}
		} catch (IOException e) {
			logger.error("Could not setup socket.", e);
		} catch (InterruptedException e) {
			logger.info("wait() interrupted.", e);
		}

		this.disconnect();
		logger.debug("Disconnected.");

		try {
			botService.scheduleReconnect(this);
		} catch (Exception e) {
			logger.error("Exception while scheduling reconnect", e);
		}
	}

	public void stop() {
		this.notify();
	}

	/**
	 * Wrapper method to easily send OutgoingMessage objects.
	 * 
	 * @param message
	 *            The message to send.
	 */
	public void send(OutgoingMessage message) {
		socket.send(message.toString());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onConnect() {
		OutgoingNickMessage nick = new OutgoingNickMessage();
		nick.setNickname(information.getNickname());
		send(nick);

		UserMessage user = new UserMessage();
		user.setUsername(information.getUsername());
		user.setRealname(information.getRealname());
		send(user);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onReceive(String line) {
		IncomingMessage message;
		try {
			message = MessageFactory.create(line);
		} catch (Exception e) {
			logger.error("Exception while parsing.", e);
			return;
		}

		logger.info("Parsed message: {}", message.getClass());

		if (message.getMessageType() == MessageType.PING) {
			PongMessage pong = new PongMessage();
			pong.setDestination(message.getContent());
			send(pong);
		}

		if (message.getMessageType() == MessageType.WELCOME_MESSAGE) {
			for (Channel channel : channels) {
				OutgoingJoinMessage join = new OutgoingJoinMessage();
				join.setChannel(channel.getName());
				join.setPassword(channel.getPassword());
				send(join);
			}
		}

		if (message.getMessageType() == MessageType.NICKNAME_IN_USE) {
			information.setNickname(information.getNickname() + "_");
			OutgoingNickMessage newNickname = new OutgoingNickMessage();
			newNickname.setNickname(information.getNickname());
			send(newNickname);
		}

		Context context = new Context(this, network);
		moduleService.dispatchCallbacks(context, message);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onSend(String line) {
		// logger.info("Sent line: {}", line);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void onDisconnect(DisconnectReason reason) {
		logger.info("Disconnected callback, reason: {}", reason);
		synchronized (this) {
			this.notify();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String toString() {
		return "IrcBot [network=" + network + ", information=" + information + "]";
	}

}
